// sw-bridge
// is-plain-object
function isObject(o) {
	return Object.prototype.toString.call(o) === "[object Object]";
}
function isPlainObject(o) {
	var ctor, prot;

	if (isObject(o) === false) return false;

	// If has modified constructor
	ctor = o.constructor;
	if (ctor === undefined) return true;

	// If has modified prototype
	prot = ctor.prototype;
	if (isObject(prot) === false) return false;

	// If constructor does not have an Object-specific method
	if (prot.hasOwnProperty("isPrototypeOf") === false) {
		return false;
	}

	// Most likely a plain Object
	return true;
}
// deep-for-each
function forEachObject(obj, fn, path) {
	for (const key in obj) {
		const deepPath = path ? `${path}.${key}` : key;

		// Note that we always use obj[key] because it might be mutated by forEach
		fn.call(obj, obj[key], key, obj, deepPath);

		forEach(obj[key], fn, deepPath);
	}
}

function forEachArray(array, fn, path) {
	array.forEach((value, index, arr) => {
		const deepPath = `${path}[${index}]`;

		fn.call(arr, value, index, arr, deepPath);

		// Note that we use arr[index] because it might be mutated by forEach
		forEach(arr[index], fn, deepPath);
	});
}

function forEach(value, fn, path) {
	path = path || "";

	if (Array.isArray(value)) {
		forEachArray(value, fn, path);
	} else if (isPlainObject(value)) {
		forEachObject(value, fn, path);
	}
}
const deepForEach = forEach;
// interfaces.js
var Action;
(function (Action) {
	Action["RunCommand"] = "run-command";
})(Action || (Action = {}));
// client.js
const __extends =
	(this && this.__extends) ||
	(function () {
		const extendStatics =
			Object.setPrototypeOf ||
			({ __proto__: [] } instanceof Array &&
				function (d, b) {
					d.__proto__ = b;
				}) ||
			function (d, b) {
				for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
			};
		return function (d, b) {
			extendStatics(d, b);
			function __() {
				this.constructor = d;
			}
			d.prototype = b === null ? Object.create(b) : ((__.prototype = b.prototype), new __());
		};
	})();
function createPendingPromise() {
	var fulfill, reject;
	const promise = new Promise(function (f, r) {
		fulfill = f;
		reject = r;
	});
	return [promise, fulfill, reject];
}
const ServiceWorkerNotSupportedError = /** @class */ (function (_super) {
	__extends(ServiceWorkerNotSupportedError, _super);
	function ServiceWorkerNotSupportedError() {
		const _this = _super.call(this, "Service workers are not supported") || this;
		Object.setPrototypeOf(_this, ServiceWorkerNotSupportedError.prototype);
		return _this;
	}
	return ServiceWorkerNotSupportedError;
})(Error);
function runServiceWorkerCommand(name, data) {
	if ("serviceWorker" in navigator === false) {
		throw new ServiceWorkerNotSupportedError();
	}
	const command = { name: name, data: data };
	const channel = new MessageChannel();
	const _a = createPendingPromise(),
		promise = _a[0],
		fulfill = _a[1],
		reject = _a[2];
	channel.port2.addEventListener("message", function (e) {
		const response = e.data;
		if (response.error) {
			reject(new Error(response.error));
		} else {
			fulfill(response.data);
		}
		channel.port2.close();
	});
	channel.port2.start();
	return navigator.serviceWorker.ready.then(function (reg) {
		reg.active.postMessage(
			{
				action: Action.RunCommand,
				command: command,
				respondOn: channel.port1,
			},
			[channel.port1]
		);
		return promise;
	});
}
// worker.js
const CommandBridgeListener = /** @class */ (function () {
	function CommandBridgeListener() {
		this.boundFunctions = {};
	}
	CommandBridgeListener.prototype.processMessage = function (e) {
		if (!e.data || !e.data.action || e.data.action !== Action.RunCommand) {
			return;
		}
		const command = e.data.command;
		const respondOn = e.data.respondOn;
		const listener = this.boundFunctions[command.name];
		if (!listener) {
			return;
		}
		Promise.resolve(listener(command.data))
			.then(function (returnData) {
				return { data: returnData };
			})
			.catch(function (error) {
				return { error: error.message };
			})
			.then(function (response) {
				const transferables = [];
				deepForEach(response, function (value) {
					if (value instanceof MessagePort || value instanceof ArrayBuffer) {
						transferables.push(value);
					}
				});
				if (response instanceof Promise) {
					response
						.then(function (t) {
							respondOn.postMessage(t, transferables);
						})
						.catch(function (e) {
							console.error(e);
							respondOn.postMessage({ error: true, errorMessage: e.toString() }, transferables);
						});
				} else {
					respondOn.postMessage(response, transferables);
				}
			});
	};
	CommandBridgeListener.prototype.bind = function (name, listener) {
		if (this.boundFunctions[name]) {
			throw new Error("Command is already bound to " + name);
		}
		this.boundFunctions[name] = listener;
	};
	CommandBridgeListener.prototype.listen = function () {
		self.addEventListener("message", this.processMessage.bind(this));
	};
	return CommandBridgeListener;
})();
const CommandListener = new CommandBridgeListener();
//
const NTUBE_SERVICE_WORKER_VERSION = "0.0.1";
const NTUBE_SERVICE_WORKER_USE_CACHE = false; // TODO: Enable me in 1.5.4 (once we get cache clearing logic done)

const cacheName = "nTube-pwa-r1";
const cacheBlacklist = [
	/* nTube: Back APIs */

	"/api/get_video",
	"/api/get_trending",
	"/api/get_sponsors",
	"/api/get_page",
	"/api/get_comments",
	"/api/channel",
	"/api/about",
	"/api/streams/video_request",
	"/api/network/throughput_test",
	"/api/pwa/present",
	"/api/experimental/get_dislikes",
	"/youtubei/v1/live_chat/get_live_chat",
	"/youtubei/v1/player/get_drm_license",

	/* nTV/nMusic (legacy) */

	"/nmusic/get",
	"/ntv/get_meta",
	"/nmusic/get",

	/* nTube: Fallback */

	"/watch_flash",
	"/watch_fallback",
	"/null",
	"/undefined",

	/* nTube: Queried URLs */

	"/watch",
	"/search",
	"/channel",
	"/playlist",
	"/embed",
	"/redirect",

	/* nTube: Front APIs */

	"/dash_translate/proxyAPI",
	"/proxyAPI",
	"/webvtt_gen",
	"/srt_gen",
	"/search_video_api",
	"/b64_dec",

	/* External Domains */

	"https://i.ytimg.com/",
	"http://i.ytimg.com/",
	"https://i9.ytimg.com/",
	"http://i9.ytimg.com/",
	"https://yt3.ggpht.com/",
	"http://yt3.ggpht.com/",
];

CommandListener.bind("sw-test", function () {
	const features = ["SW_QUERY_FEATURES"];
	if (NTUBE_SERVICE_WORKER_USE_CACHE) {
		features.push("SW_PWA_CACHE");
		features.push("SW_PWA_RESET_CACHE");
	}
	return {
		error: false,
		version: NTUBE_SERVICE_WORKER_VERSION,
		features: features,
		for: "sw-test",
	};
});
CommandListener.bind("reset-pwa-cache", async function () {
	if (!NTUBE_SERVICE_WORKER_USE_CACHE) {
		return {
			error: false,
			ok: false,
			reason: "NTUBE_PWA_DISABLED",
			for: "reset-pwa-cache",
		};
	}
	await caches.keys().then((keyList) => {
		return Promise.all(
			keyList.map((key) => {
				if (key === cacheName) {
					return;
				}
				return caches.delete(key);
			})
		);
	});
	return {
		error: false,
		ok: true,
		for: "reset-pwa-cache",
	};
});
CommandListener.bind("is-instance-active", async function () {
	const r = await fetch("./api/pwa/present");
	const json = await r.json();

	return {
		error: false,
		resp: json,
		for: "is-instance-active",
	};
});

self.addEventListener("install", function () {
	console.log("[nTube-SW] Started service worker version " + NTUBE_SERVICE_WORKER_VERSION);
	self.skipWaiting();
});

self.addEventListener("activate", function () {
	self.clients.claim();
});

if (NTUBE_SERVICE_WORKER_USE_CACHE) {
	self.addEventListener("fetch", function (e) {
		e.respondWith(
			(async function () {
				const url = e.request.url;
				if (cacheBlacklist.some((item) => url.includes(item))) {
					const uc = await fetch(e.request);
					return uc;
				}
				const r = await caches.match(e.request);
				//    		console.debug(`[nTube-SW] Fetching resource: ${url}`);
				if (r) {
					return r;
				}
				const response = await fetch(e.request);
				const cache = await caches.open(cacheName);
				console.debug(`[nTube-SW] Caching new resource: ${url}`);
				cache.put(e.request, response.clone());
				return response;
			})()
		);
	});
}

CommandListener.listen();
